<shape xmlns:android="http://schemas.android.com/apk/res/android"
//設置shape為長方形
android:shape="rectangle">
//設置圓角半徑
<corners android:radius="20dp" />
//設置文字與Button邊界距離
<padding
android:bottom="10dp"
android:left="10dp"
android:right="10dp"
android:top="10dp" />
//設置Button顏色
<solid android:color="#48CFAD" />
</shape>
取得相機、讀取/寫入外部儲存裝置的權限
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
從 官方文件 可以找到這三個權限的 Protection level 皆為:
Protection level: dangerous
因此必須在 App 開啟時向使用者 請求權限
fun permission(){
ActivityCompat.requestPermissions(this, arrayOf(
android.Manifest.permission.CAMERA,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
android.Manifest.permission.READ_EXTERNAL_STORAGE), 0)
}
可以用下列方法檢查使用者目前為拒絕還是允許該權限
checkSelfPermission (context: Context, permission: String)
ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA)
此方法會得到兩種可能結果
PackageManager.PERMISSION_GRANTED 允許
PackageManager.PERMISSION_DENIED 拒絕
API 24 版本以上,Android 不再允許在 app 中透漏 file://Uri 給其他 app ( 官方文件 )
因此 google 提供了 FileProvider 來生成 content://Uri 取代 file://Uri
FileProvider 將隱藏真實的共享檔案路徑,並將路徑轉換為 content:// Uri
在 AndroidManifest.xml 中加上 provider
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.aria.day3_image_pickerintentimageview.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
android:authorities:
用來標識 provider 的唯一標誌,同一台手機中一個 authority 名稱只能被一個 app 使用
android:exported:必須設為 false,因為 FileProvider 不應被設置為公開
android:grantUriPermissions:控制共享文件的訪問權限
android:resource:於 xml 路徑下設置的 provider_paths.xml
在 res/xml/ 下新增 provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
此檔案為 file://Uri 轉換為 content://Uri 的規則
寫法: <files-path path="images/" name="my_images" />
files-path:
共享文件路徑的子元素,一份 provider_paths.xml 需含一至多個下列元素
文件路徑下的子元素 | 對應路徑 |
---|
files-path | 內部儲存空間應用私有目錄下的 files/ 目錄,等同 Context.getFilesDir() 獲取的路徑
cache-path | 內部儲存空間應用私有目錄下的 cache/ 目錄,等同 Context.getCacheDir() 獲取的路徑
external-path | 外部儲存空間的根目錄,等同 Environment.getExternalStorageDirectory() 獲取的路徑
external-files-path | 外部儲存空間應用私有目錄下的 files/ 目錄,等同 Context.getExternalFilesDir(null) 獲取的路徑
external-cache-path | 外部儲存空間應用私有目錄下的 cache/ 目錄,等同 Context.getExternalCacheDir() 獲取的路徑
path:指定當前子元素目錄下需要共享的子目錄名稱
name:用來取代上述 path 指定的目錄名稱的別名
上述寫法表示:
將 Context.getFilesDir() 獲取的路徑下的目錄 images/ 取代為別名 my_images
轉換示意圖(圖片來源:參考資料 1)
取得 content://Uri
定義 File
取得 uri
FileProvider.getUriForFile(context, authorities, file)
#此 authorities 和 AndroidManifest.xml 的 provider 中設定的 authorities 相同即可
設定 componion object 參數作為 intent 回傳的 requestCode,供 onActivityResult 判斷
private companion object {
val PHOTO_FROM_GALLERY = 1
val PHOTO_FROM_CAMERA = 2
}
intent to Album
要從其他應用程式擷取檔案有兩種方式:
intent 必須 setType ,透過指定的 MIME Type 取得相對應類型的檔案
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.setType("image/*")
intent 不一定要 setType,但須設置 Uri 並透過指定的 Uri 來取得該目錄下的檔案
val uri = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_Uri
val intent = Intent(Intent.ACTION_PICK, uri)
設定完 intent 後 startActivityForResult
startActivityForResult ( intent : Intent , requestCode : Int)
startActivityForResult(intent, PHOTO_FROM_GALLERY)
intent to Camera
MediaStore . ACTION_IMAGE_CAPTURE
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
設置並提供 Camera 存放照片的 Uri
lateinit var saveUri: Uri //外部定義變數
val tmpFile = File(Environment.getExternalStorageDirectory().toString(), System.currentTimeMillis().toString() + ".jpg")
val uriForCamera = FileProvider.getUriForFile(this, "com.example.aria.day3_image_pickerintentimageview.fileprovider", tmpFile)
saveUri = uriForCamera //將 Uri 存進變數供後續 onActivityResult 使用
intent.putExtra(MediaStore.EXTRA_OUTPUT, uriForCamera)
透過 intent.putExtra 的方式,通知相機新照片的儲存位置(Uri)
KEY 為官方設定好的 MediaStore.EXTRA_OUTPUT
#若 intent 至相機時"無"提供 Uri:
照片會以 intent.putextra("data", bitmap) 的方式回傳至 result
#若 intent 至相機時"有"提供 Uri:
則 result 回傳的 data 會為 null , 故需自行另存 Uri 以供後續讀圖使用
設定完 intent 後 startActivityForResult
startActivityForResult(intent, PHOTO_FROM_CAMERA)
onActivityResult( requestCode: Int, resultCode: Int, data: Intent? )
取得 Intent 回來的結果並處理事件
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
PHOTO_FROM_GALLERY -> {
when (resultCode) {
Activity.RESULT_OK -> { }
Activity.RESULT_CANCELED -> { }
}
}
PHOTO_FROM_CAMERA -> {
when (resultCode) {
Activity.RESULT_OK -> { }
Activity.RESULT_CANCELED -> { }
}
}
}
}
requestCode:前面設置用來辨別是哪個 intent 回傳的結果
resultCode:確認 intent 成功與否
data :intent 帶回的資料
相簿回傳處理
val uri = data!!.data
imageView.setImageURI(uri)
#相簿會以 Intent( action: String, uri: Uri) 的方式回傳 Uri,
須用 data.data 的方式取得 Uri#data.data = data.getData()
相機回傳處理
為避免產生 OOM 問題,使用 Glide 套件載入圖片至 imageView
欲使用 Glide 套件需先在 Gradle 中加入
repositories {
mavenCentral()
google()
}
dependencies {
implementation 'com.github.bumptech.glide:glide:4.8.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
}
透過 Glide 將前面存好的 Uri 載入至 imageView
Glide.with(this).load(saveUri).into(imageView)
#若前面無提供 Uri 給相機,
會以 intent.putextra("data", bitmap) 的方式回傳 bitmap
須用 data.extra.get("data") 方式取得
Album
Camera
Permission
FileProvider
onActivityResult
Uri
Glide